/**
 * http request client.
 */
import Taro from '@tarojs/taro';
import * as checkType from './checkType';
import deepmerge from "./deepmerge";
import prompt from "../../prompt";

interface StrToAnyObj {
  [index: string]: any
}

export type Resp<T> = { code: string | number, data: T, msg: string }
export type PromiseResp<T> = Promise<Resp<T>>
type RequestType = "request" | "upload" | "download";   // 请求类型
interface RequestOtherOptions {
  isFormatOriginRespData?: boolean;   // 是否直接包装原始的响应数据
  isShowErrorPrompt?: boolean;
}

export interface RequestOptions extends Taro.request.Option, RequestOtherOptions {
}

export interface UploadOptions extends Taro.uploadFile.Option, RequestOtherOptions {
  onProgressUpdate?: Taro.UploadTask.OnProgressUpdateCallback, // 监听上传进度变化事件回调方法
}

export interface DownloadOptions extends Taro.downloadFile.Option, RequestOtherOptions {
  onProgressUpdate?: Taro.DownloadTask.OnProgressUpdateCallback, // 监听下载进度变化事件回调方法
}

// TODO 解决IE报warning Unhandled Rejections Error 参数书不正确的问题
// @ts-ignore
Promise._unhandledRejectionFn = function (rejectError) { };

const tokenStoreKey = "requestToken";  // token的存储token key

interface IRequestConf {
  apiDomain?: string,
  apiResponseCode?: {
    apiSuccessCode?: string,
    errorCode?: string,
    noLoginCode?: string,
    noPermissionCode?: string,
    invalidParamCode?: string
  },
  redirectLogin?: () => void,
  onAfterResp?: (respInfo: Resp<any>) => void,
  requestToken?: string;  // 请求认证token,用于类似小程序中的webview不能持久保持cookie,从而在请求header中添加token的方式来管理
}

let requestConf: IRequestConf = {
  apiDomain: "",
  apiResponseCode: {
    apiSuccessCode: "200",
    errorCode: "error",
    noLoginCode: "401",
    noPermissionCode: "403",
    invalidParamCode: "400"
  },
  redirectLogin: () => { },
  onAfterResp: () => { }
}

/**
 * 设置请求配置
 */
export const setRequestConf = (config: IRequestConf) => {
  requestConf = deepmerge(requestConf, config);
}

export const getRequestConf = () => requestConf;


/**
 * 保存token
 */
export const setToken = (token: string) => {
  Taro.setStorageSync(tokenStoreKey, token);

}

/**
 * 获取token
 */
export const getToken = () => {
  return Taro.getStorageSync(tokenStoreKey) as string;
}

/**
 * 删除token
 */
export const removeToken = () => {
  Taro.removeStorageSync(tokenStoreKey)
}

/**
 * 格式化url地址
 */
const formatUrl = (url: string) => {
  if (/^http/.test(url)) return url;
  const { apiDomain } = requestConf;
  return apiDomain?.replace(/\/$/, "") + "/" + url.replace(/^\//, "");
}

/**
 * 执行请求
 */
const _request = <T=any>(options: RequestOptions | UploadOptions | DownloadOptions, requestType: RequestType = "request"): PromiseResp<T> => new Promise((resolve, reject) => {
  const { redirectLogin, apiResponseCode, onAfterResp } = requestConf;
  const { apiSuccessCode = "", errorCode, noLoginCode, invalidParamCode } = apiResponseCode || {};
  const unknownErrMsg = "未知错误";
  const rejectError = (data: any) => {
    if (options.isShowErrorPrompt) prompt.error(data.msg || unknownErrMsg, 2000)
    reject(data);
  }

  options.url = formatUrl(options.url);
  // 设置请求header中的认证token
  const token = getToken();
  if (token) {
    options.header = {
      ...options.header,
      Authorization: `Bearer ${token}`
    }
  }

  // 响应成功回调
  const success: Taro.request.Option["success"] = (resp) => {
    const { statusCode } = resp;
    if (statusCode == 200) {
      // 因为上传方法，会将其格式化为字符串，所以在这做数据格式化
      if (requestType == "upload"){
        try{
          resp.data = JSON.parse(resp.data);
        }catch(e){
          console.error("格式化上传响应数据报错:", e)
        }
      }

      if (options.isFormatOriginRespData) {
        return resolve({ code: apiSuccessCode, data: resp.data, msg: "请求成功" });
      }

      if (checkType.isPlainObject(resp.data)) {
        let { data, code, message, msg } = resp.data;
        msg = msg || message;
        const respInfo = { code, data, msg };
        onAfterResp && onAfterResp(respInfo);
        if (apiSuccessCode == code || (Array.isArray(apiSuccessCode) && apiSuccessCode.map((code) => code.toString()).includes(code.toString()))) {
          return resolve({ code, data, msg });
        } else if (code == noLoginCode) {  // 未登录
          redirectLogin && redirectLogin();
          return rejectError({ code, data, msg });
        } else if (code == invalidParamCode) { // 参数校验失败
          return rejectError({ code, data, msg });
        } else { // 系统内部错误
          return rejectError({ code, data, msg });
        }
      }

      onAfterResp && onAfterResp({ code: apiSuccessCode, data: resp.data, msg: "请求成功" });
      resolve({ code: apiSuccessCode, data: resp.data, msg: "请求成功" });
    } else if (statusCode == parseInt(noLoginCode ?? "")) {
      redirectLogin && redirectLogin();
      return rejectError({ code: statusCode, data: null, msg: "请先登录" });
    } else {
      const { code, msg } = resp.data;
      // @ts-ignore
      const respInfo = { code, data: null, msg: msg || resp.message }

      onAfterResp && onAfterResp(respInfo);
      rejectError(respInfo);
    }
  }

  // 响应失败回调
  const fail: Taro.request.Option["fail"] = (resp) => {
    const { errMsg } = resp;
    const respInfo = {
      code: errorCode ?? "error",
      data: null,
      msg: errMsg
    }
    onAfterResp && onAfterResp(respInfo);
    rejectError(respInfo);
  }

  switch (requestType) {
    // 处理普通请求
    case "request":{
      const realOptions = options as RequestOptions;

      Taro.request({
        success,
        fail,
        ...realOptions
      })
      break;
    }
    // 处理上传文件请求
    case "upload":{
      const realOptions = options as UploadOptions;
      const uploadTask = Taro.uploadFile({
        success,
        fail,
        ...realOptions
      });

      // 监听上传进度变化事件
      if(realOptions.onProgressUpdate) uploadTask.onProgressUpdate(realOptions.onProgressUpdate);
      break;
    }
    // 处理下载文件请求
    case "download":{
      const realOptions = options as DownloadOptions;
      const downloadTask = Taro.downloadFile({
        success:(resp) => {
          const { filePath, tempFilePath, statusCode, errMsg, dataLength } = resp;
          if (statusCode == 200){
            const data: any = {filePath, tempFilePath,dataLength}
            resolve({data, msg: "下载成功", code: statusCode})
          }else{
            rejectError(errMsg);
          }
        },
        fail,
        ...realOptions
      });

      // 监听下载进度变化事件
      if(realOptions.onProgressUpdate) downloadTask.onProgressUpdate(realOptions.onProgressUpdate);

      break;
    }
  }
})


/**
 * get请求
 */
export function get<T = any>(url: string, params?: any, options?: Partial<RequestOptions>) {
  return _request<T>({
    isShowErrorPrompt: true,
    url,
    method: "GET",
    data: params,
    ...options,
  });
}

/**
 * post请求
 */
export function post<T = any>(url: string, params: StrToAnyObj = {}, options?: Partial<RequestOptions>) {
  let requestParams = new URLSearchParams();
  for (let [k, v] of Object.entries(params)) {
    requestParams.append(k, v);
  }

  return _request<T>({
    isShowErrorPrompt: true,
    url,
    method: "POST",
    data: params,
    header: {
      'content-type': 'application/x-www-form-urlencoded'
    },
    ...options,
  });
}

/**
 * post json请求
 */
export function postJSON<T = any>(url: string, params: any = {}, options?: Partial<RequestOptions>) {

  return _request<T>({
    isShowErrorPrompt: true,
    url,
    method: "POST",
    data: params,
    header: {
      'content-type': 'application/json'
    },
    ...options,
  });
}

/**
 * restful delete
 */
export function del<T = any>(url: string, params: any = {}, options?: Partial<RequestOptions>) {
  return _request<T>({
    isShowErrorPrompt: true,
    url,
    method: "DELETE",
    data: params,
    header: {
      'content-type': 'application/json'
    },
    ...options,
  });
}

/**
 * restful put
 */
export function put<T = any>(url: string, params: any = {}, options?: Partial<RequestOptions>) {
  return _request<T>({
    isShowErrorPrompt: true,
    url,
    method: "PUT",
    data: params,
    header: {
      'content-type': 'application/json'
    },
    ...options,
  });
}

/**
 * 上传文件
 */
export function upload<T = any>(url: string, params:{name: string, filePath: string}, formData: StrToAnyObj = {}, options?: Partial<UploadOptions>) {
  return _request<T>({
    isShowErrorPrompt: true,
    url,
    filePath: params.filePath,
    name: params.name,
    data: params,
    formData,
    ...options
  }, "upload");
}

/**
 * 下载文件
 */
export function download(url: string,options?: Partial<DownloadOptions>) {
  return _request<{filePath: string, tempFilePath: string,dataLength: number}>({
    isShowErrorPrompt: true,
    url,
    ...options
  }, "download");
}


// /**
//  * 利用dql查询数据
//  */
// export function getByDQL<T = any>(queryParams: { dqlKey: string, uri?: string, params?: { [index: string]: any }, isFilterEmptyParams?: boolean }) {
//   const { dqlKey, uri, params = {}, isFilterEmptyParams } = Object.assign({ uri: "data/search", isFilterEmptyParams: true }, queryParams);
//   const realParams = { ...params };
//   if (isFilterEmptyParams) {
//     Object.keys(realParams).forEach((key) => {
//       if (realParams[key] === "") delete realParams[key];
//     });
//   }

//   return postJSON<T>(`${requestConf?.apiDomain?.replace(/\/$/, "")}/${uri.replace(/^\//, "")}?dqlKey=${dqlKey}`, { params: realParams })
// }


/**
 * 并发执行多个请求
 * @returns {Promise.<*>}
 */
export function all<T = any[]>(args: Promise<T>[] = []) {
  return Array.isArray(args) ? Promise.all(args) : Promise.all([...arguments]);
}

/**
 * 格式化URL参数
 */
export function formatUrlParams(url: string, params: StrToAnyObj = {}) {
  Object.keys(params).forEach((key, index) => {
    if (index === 0 && url.indexOf('?') === -1) {
      url += '?' + key + '=' + params[key];
    } else {
      url += '&' + key + '=' + params[key];
    }
  });

  return url;
}

/**
 * 模拟响应数据
 */
export function mockRespData<T = any>(data: T, isSuccess = true, timeout = 500): PromiseResp<T> {
  const { apiSuccessCode, errorCode } = requestConf.apiResponseCode || {};

  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (isSuccess) {
        resolve({
          code: apiSuccessCode ?? "200",
          msg: "success",
          data
        })
      } else {
        reject({
          code: errorCode ?? "200",
          msg: "error",
          data
        })
      }
    }, timeout);
  })
}


/**
 * 生成响应数据
 * @return {Promise<any>}
 */
export function mkRespData<T = any>(data: T, code = "success", msg = "success") {
  return {
    code,
    msg,
    data
  }
}
